package com.ming.tflitesingleblur;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.EGL14;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.Build;
import android.util.Log;

import com.ming.tflitesingleblur.segment.Classifier;
import com.ming.tflitesingleblur.utils.ImageSaveUtils;
import com.ming.tflitesingleblur.utils.Utils;
import com.ming.tflitesingleblur.utils.texturerecoder.TextureMovieEncoder;

import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_FRAMEBUFFER;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindFramebuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenFramebuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;

/**
 * Created by glue.wang on 2017/12/27.
 * e-mail:miniminiming@qq.com
 * 总结一下：首先创建GLSurfaceView和开启相机，之后创建一个外部纹理，根据此纹理ID创建一个SurfaceTexture，
 * Camera将预览数据输出至此SurfaceTexture上，执行SurfaceTexture.updateTexImage()就会将一帧预览数据
 * 推送给外部纹理上。之后OpenGL ES就可以操作此纹理，比如加滤镜，滤镜就是对纹理的RGBA通道进行处理，
 * 处理后的数据就通过OpenGL ES绘制出来，其实对于OpenGL ES来说，最终就是画两个三角形，三角形的颜色取自
 * 纹理对应的位置。
 */

class CameraGL20Render implements GLSurfaceView.Renderer {


    //顶点坐标，我这里要把顶点坐标和纹理坐标分开放
    //这里，把矩形的屏幕分割成了两个三角形，因为是平面图
    private final float[] vertexData = {
            1f, 1f,
            -1f, 1f,
            -1f, -1f,

            1f, 1f,
            -1f, -1f,
            1f, -1f,
    };

    Context mContext;
    SurfaceTexture mSurfaceTexture;
    String TAG = "CameraRender";
    //忽略了一个问题，就是纹理的坐标系，纹理的坐标系并不是跟世界坐标系重合的
    //它的坐标系以屏幕左下角为0,0点，右为x+,上为y+所以纹理坐标如下
    //右上角坐标为：1,1
    //注意，这里的顺序一定要和上面的顶点坐标对应
    private final float[] textureData = {
            1, 1,
            0, 1,
            0, 0,

            1, 1,
            0, 0,
            1, 0
    };

    FloatBuffer mVertexBuffer;
    FloatBuffer mTextureBuffer;

    int mTextureId;
    int mShaderProgram;
    float[] transformMatrix;
    float[] maskTransformMatrix;
    private int[] mFBOIds = new int[1];//这是什么？
    Camera mCamera;
    GLSurfaceView mGLSurfaceView;

    public CameraGL20Render(Context context,
                            GLSurfaceView glSurfaceView,
                            Camera camera) {
        mContext = context;
        mGLSurfaceView = glSurfaceView;
        mCamera = camera;
    }

    public boolean initSurfaceTexture() {
        //根据外部纹理ID创建SurfaceTexture
        //尼玛，搅和了半天，原来是纹理没设置正确，丢雷楼某啊
        mSurfaceTexture = new SurfaceTexture(mTextureId);
        mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                //每获取到一帧数据时请求OpenGL ES进行渲染
                //这也是个一直在循环被调用的回调
//                mSurfaceTexture.updateTexImage();//配合它一起使用的？已确定
//                主动 “拉”这个重绘过程，有点类似于Canvas中的invalidate（）
                /*try {
                //这个sleep证明了这个循环过程
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
//                mGLSurfaceView.requestRender();
//                Log.i(TAG, "onFrameAvailable===========");
            }
        });
        //讲此SurfaceTexture作为相机预览输出
        try {
            mCamera.setPreviewTexture(mSurfaceTexture);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //开启预览
        mCamera.startPreview();
        return true;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //注意，使用了gl2.0的api以后就不要使用GL10了，否则会报错，虽然不崩溃，但是会出一些异常信息
        //初始化纹理id，顶点数据，纹理数据
        mTextureId = Utils.createOESTextureObject();
        mVertexBuffer = Utils.createBuffer(vertexData);
        mTextureBuffer = Utils.createBuffer(textureData);
        transformMatrix = new float[16];
        maskTransformMatrix = new float[16];
        Matrix.setIdentityM(maskTransformMatrix, 0);
        //调整一下mask的方位
        Matrix.rotateM(maskTransformMatrix,0,180, 0, 0, 1);
        Matrix.rotateM(maskTransformMatrix,0,180, 0, 1, 0);


        /**
         * Android上Opengl是通过Vertex Shader 和 Fragment Shader 这两种定点着色器程序来实现图片
         * 的加载和渲染的，中文称为定点着色器和片段着色器。一个完整的Opengl程序需要创建定点着色器和片
         * 段着色器并将他们Link起来组成一个完整的OpenGL程序。
         */
        int vertexShader = Utils.loadShader(GL_VERTEX_SHADER,
                Utils.readShaderFromResource(mContext, R.raw.base_vertex_shader));
        int fragmentShader = Utils.loadShader(GL_FRAGMENT_SHADER,
                Utils.readShaderFromResource(mContext, R.raw.base_fragment_shader));
//        int vertexShader = Utils.loadShader(GL_VERTEX_SHADER,
//                Utils.readShaderFromResource(mContext, R.raw.vertex_back));
//        int fragmentShader = Utils.loadShader(GL_FRAGMENT_SHADER,
//                Utils.readShaderFromResource(mContext, R.raw.frag_back));

        mShaderProgram = Utils.linkProgram(vertexShader, fragmentShader);

        //看名字好像是个帧缓冲区，但是加上没有明显变化,跟后面的解绑对应
        //帧缓冲区对象呢又称为FBO，它允许我们把渲染从窗口的帧缓冲区转移
        // 到我们所创建的一个或者多个离屏帧缓冲区
        glGenFramebuffers(1, mFBOIds, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, mFBOIds[0]);
        Log.i(TAG, "onSurfaceCreated============");
        boolean ispreview = initSurfaceTexture();
        Log.i(TAG, "preview================" + ispreview);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //先不使用10的api试试
        glViewport(0, 0, width, height);
        Log.i(TAG, "onSurfaceChanged============");
    }

    private float[] maskData;
    private Bitmap mMaskBitmap;

    //只能在gl线程调用
    public void setMaskData(Classifier classifier, float[] maskData) {
        this.maskData = maskData;
        //生成mask的纹理
        if(maskData != null){

             mMaskBitmap = ImageSaveUtils.convertDataToBitmap(maskData,
                     new int[]{classifier.getOutputWidth(), classifier.getImageHeight(), 1},
                    true,
                    new float[]{0, 255f});

            Log.i(TAG, "setMaskData============   generate mask texture " + maskTextureId);
        }

    }

    int maskTextureId = -1;
    @Override
    public void onDrawFrame(GL10 gl) {
        glClearColor(0, 0, 0, 1);
        //事实证明，混用确实会造成一些异常日志
        // libEGL: called unimplemented OpenGL ES API
        // 清除屏幕和深度缓存
//        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        // 重置当前的模型观察矩阵
//        gl.glLoadIdentity();

        //到这里相机已经可以将预览数据发送到SurfaceTexture上，并且此预览数据实际上是
        //填充到了SurfaceTexture绑定的外部纹理中
        /**所以这里是这样的一个循环，mGLSurfaceView.requestRender()引起onDrawFrame(GL10 gl)的
         * 回调，然后调用mSurfaceTexture.updateTexImage();引发mSurfaceTexture.setOnFrameAvailab
         * leListener的回调，然后我们在回调里面再次调用mGLSurfaceView.requestRender()
         */
        if (mSurfaceTexture != null) {
            //更新纹理图像
            /**
             * 无论是自动模式还是手动模式，这个方法都必须调用，相机把数据放在这个mSurfaceTexture里，它多少
             帧我们不知道，我们这边只要去这里取数据即可，所以这里数据流为
             1.相机获取的数据，通过setPreviewTexture(mSurfaceTexture)指定的纹理表面
             2.相机预览时把数据放到上面指定的纹理中
             3.然后，当我们去mGLSurfaceView.requestRender()请求渲染的时候，
             先更新一下纹理数据(并非自动) mSurfaceTexture.updateTexImage()
             4.再然后，使用指定的纹理id绘制——glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)
             就从上面含有数据的纹理中获取数据并且绘制了
             */

            mSurfaceTexture.updateTexImage();//调用，更新纹理数据
            //获取外部纹理的矩阵，用来确定纹理的采样位置，没有此矩阵可能导致图像翻转等问题
            mSurfaceTexture.getTransformMatrix(transformMatrix);
        }



        //获取属性的句柄
        int aPositionLocation = glGetAttribLocation(mShaderProgram, "aPosition");
        int aTextureCoordLocation = glGetAttribLocation(mShaderProgram, "aTextureCoordinate");
        int uTextureMatrixLocation = glGetUniformLocation(mShaderProgram, "uTextureMatrix");
        int uMaskTextureMatrixLocation = glGetUniformLocation(mShaderProgram, "uMaskTextureMatrix");
        int uTextureSamplerLocation = glGetUniformLocation(mShaderProgram, "uTextureSampler");
        int uMaskTextureSamplerLocation = glGetUniformLocation(mShaderProgram, "sMaskTexture");

        //激活纹理，相当于启用纹理
        glUniform1i(uTextureSamplerLocation, 0);
        glActiveTexture(GLES20.GL_TEXTURE0);
        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
        glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);
        glUniformMatrix4fv(uMaskTextureMatrixLocation, 1, false, maskTransformMatrix, 0);

        //mask纹理
        if(mMaskBitmap != null){
            if (maskTextureId != -1) {//若当前纹理存在，则删除
                GLES20.glDeleteTextures(1, new int[]{maskTextureId}, 0);
            }
            maskTextureId = initTexture(mMaskBitmap);
            //这里的第二个参数一定要是1，与前面的location区分开，草，多个纹理不能共用
            glUniform1i(uMaskTextureSamplerLocation, 1);
            glActiveTexture(GLES20.GL_TEXTURE1);
            glBindTexture(GLES20.GL_TEXTURE_2D, maskTextureId);
        }

        if (mVertexBuffer != null) {//其实分开使用更容易理解一些
            mVertexBuffer.position(0);
            glEnableVertexAttribArray(aPositionLocation);
            glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, mVertexBuffer);

            mTextureBuffer.position(0);
            glEnableVertexAttribArray(aTextureCoordLocation);
            glVertexAttribPointer(aTextureCoordLocation, 2, GL_FLOAT, false, 0, mTextureBuffer);
        }

        //后面的这个6，指的是绘制6个顶点，如果你设置成3，就表示只使用前三个顶点，那么在这里只会绘制一个
        //三角形，那么就会只显示一半图像
        glDrawArrays(GL_TRIANGLES, 0, 6);

        //在使用完帧缓存对象的最后，对帧缓存对象进行解绑
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
//        Log.i(TAG, "onDrawFrame============");
        Log.i(TAG, "onDrawFrame:  error = " + GLES20.glGetError());;
        //加个录制视频功能
//        recordVideo();
    }

    //生成纹理的id
    public int initTexture(Bitmap bitmap) {
        //生成纹理ID
        int[] textures = new int[1];
        GLES20.glGenTextures
                (
                        1,          //产生的纹理id的数量
                        textures,   //纹理id的数组
                        0           //偏移量
                );
        int textureId = textures[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

        //实际加载纹理
        GLUtils.texImage2D
                (
                        GLES20.GL_TEXTURE_2D,   //纹理类型
                        0,                      //纹理的层次，0表示基本图像层，可以理解为直接贴图
                        bitmap,              //纹理图像
                        0                      //纹理边框尺寸
                );
//        bitmap.recycle();          //纹理加载成功后释放图片

        return textureId;
    }


    private void recordVideo() {
        if (mTextureMovieEncoder == null) {
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            if (mTextureMovieEncoder != null &&
                    mTextureMovieEncoder.checkRecordingStatus(mTextureMovieEncoder.START_RECORDING)) {
                File outFile = new File("/sdcard/workspace/cube.mp4");
                mTextureMovieEncoder.startRecording(new TextureMovieEncoder.EncoderConfig(
                        outFile,
                        720,
                        1080,
                        5000000,
                        EGL14.eglGetCurrentContext(), //从上下文获取数据源
                        System.nanoTime()
                ));
            }

            if (mTextureMovieEncoder != null && mTextureMovieEncoder.enableRecord()) {
                recordFrame();
                mTextureMovieEncoder.nextFrame();
//                mTextureMovieEncoder.frameAvailable(mSurfaceTexture.getTimestamp());
                mTextureMovieEncoder.frameAvailable(System.nanoTime());
            }
        }
    }

    private static final int DEFAULT_RENDER_WIDTH = 720;
    private static final int DEFAULT_RENDER_HEIGHT = 1080;

    private void recordFrame() {
        if (mTextureMovieEncoder.getTextureID() == 0) {
            Log.e(TAG, "mTextureMovieEncoder.getTextureID()==0");
            return;
        }
        int viewport[] = new int[4];
        GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, viewport, 0);

        //这里应该是录制的核心函数，绑定帧缓冲
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mTextureMovieEncoder.getFrameBufferID());
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mTextureMovieEncoder.getTextureID(), 0);

        GLES20.glViewport(0, 0, DEFAULT_RENDER_WIDTH, DEFAULT_RENDER_HEIGHT);
        //外面的绘制归外面的显示绘制，这里的绘制则是录制功能的绘制
        drawRecordFrame();

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        GLES20.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
    }
    private void drawRecordFrame() {
        //获取属性的句柄
        int aPositionLocation = glGetAttribLocation(mShaderProgram, "aPosition");
        int aTextureCoordLocation = glGetAttribLocation(mShaderProgram, "aTextureCoordinate");
        int uTextureMatrixLocation = glGetUniformLocation(mShaderProgram, "uTextureMatrix");
        int uTextureSamplerLocation = glGetUniformLocation(mShaderProgram, "uTextureSampler");

        //激活纹理，相当于启用纹理
        glActiveTexture(GLES20.GL_TEXTURE0);
//        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, recordTexture);//所以这个地方用谁是没区别的
        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);

        glUniform1i(uTextureSamplerLocation, 0);
        glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);

        if (mVertexBuffer != null) {//其实分开使用更容易理解一些
            mVertexBuffer.position(0);
            glEnableVertexAttribArray(aPositionLocation);
            glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, mVertexBuffer);

            mTextureBuffer.position(0);
            glEnableVertexAttribArray(aTextureCoordLocation);
            glVertexAttribPointer(aTextureCoordLocation, 2, GL_FLOAT, false, 0, mTextureBuffer);
        }

        //后面的这个6，指的是绘制6个顶点，如果你设置成3，就表示只使用前三个顶点，那么在这里只会绘制一个
        //三角形，那么就会只显示一半图像
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }

    public TextureMovieEncoder mTextureMovieEncoder;


    public void startRecord() {
        //加个录制视频功能
        mTextureMovieEncoder = new TextureMovieEncoder();
    }

    public void stopRecord() {
        //停止录制
        if (mTextureMovieEncoder != null && mTextureMovieEncoder.checkRecordingStatus(TextureMovieEncoder.IN_RECORDING)) {
            mTextureMovieEncoder.stopRecording();
        }
    }
}
